#include "ClickScene.h"
#include "Scene.h"

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define POS(a) MAX(a, 0)

using namespace CG;
using namespace LA;

// 根据类型创建并返回
ClickScene *ClickScene::Create(ClickType type)
{
    ClickScene *cs = nullptr;
    switch (type)
    {
    case ClickType::Click_Mesh:
    {
        cs = new ClickMesh;
        break;
    }
    }
    return cs;
}

// 根据屏幕坐标，检查此位置是否与网格投影重合，返回 z 值最大的重合的网格索引
void ClickMesh::React(Scene *scene)
{
    double z = 1;

    // 直接通过相机获得屏幕坐标
    Camera *camera = scene->m_camera;
    int x = camera->m_screenX;
    int y = camera->m_screenY;

    // 初始化选中索引
    scene->m_data.choose = -1;

    // 鼠标点的坐标
    CRect rect;
    scene->m_pWnd->GetClientRect(&rect);
    point3f np = {2.0 * x / rect.Width() - 1, 1 - 2.0 * y / rect.Height(), 0};

    // 遍历所有网格，计算投影
    for (int i = 0; i < scene->m_mList.size(); i++)
    {
        bounding_box box = scene->m_mList[i]->m_box;

        // 获得顶点，创建一个临时网格。注意要先投影到正交视景体当中
        Mesh mesh;
        vertex3f *v1 = mesh.CreateVertex(camera->MapstoOrtho({box.left, box.top, box.zNear}));
        vertex3f *v2 = mesh.CreateVertex(camera->MapstoOrtho({box.right, box.top, box.zNear}));
        vertex3f *v3 = mesh.CreateVertex(camera->MapstoOrtho({box.right, box.top, box.zFar}));
        vertex3f *v4 = mesh.CreateVertex(camera->MapstoOrtho({box.left, box.top, box.zFar}));
        vertex3f *v5 = mesh.CreateVertex(camera->MapstoOrtho({box.left, box.bottom, box.zNear}));
        vertex3f *v6 = mesh.CreateVertex(camera->MapstoOrtho({box.right, box.bottom, box.zNear}));
        vertex3f *v7 = mesh.CreateVertex(camera->MapstoOrtho({box.right, box.bottom, box.zFar}));
        vertex3f *v8 = mesh.CreateVertex(camera->MapstoOrtho({box.left, box.bottom, box.zFar}));

        // 注意这里创建面的顺序经过设计，确保每次创建的面都相邻，防止出现问题
        mesh.CreateFace(v1, v2, v3, v4);
        mesh.CreateFace(v2, v6, v7, v3);
        mesh.CreateFace(v1, v5, v6, v2);
        mesh.CreateFace(v1, v4, v8, v5);
        mesh.CreateFace(v4, v3, v7, v8);
        mesh.CreateFace(v8, v7, v6, v5);
        // 这是此网格的包围盒的投影网格

        // 直接沿着 z 轴正向对网格进行投影得到的边界顶点
        std::deque<vertex3f *> vList = mesh.MeshProject({0, 0, 1});

        // 获得这些顶点的凸多边形
        std::deque<point3f> polygon;
        double tz = 1;
        for (int i = 0; i < vList.size(); i++)
        {
            point3f p = vList[i]->pos;

            // 将 z 保存为最靠前的值
            tz = MIN(p.z, tz);
            p.z = 0;

            // 记录投影后将 z 值归零的结果
            polygon.push_back(p);
        }

        // 如果鼠标点在此凸包内，并且它更靠前，就选择这个网格
        if (isInHull(np, polygon) && tz < z)
        {
            z = tz;
            scene->m_data.choose = i;
        }
    }
}